package action

import (
	"fmt"
	"github.com/Ming0706/gorm/internal"
	"github.com/jinzhu/gorm"
	"time"
)

/*	GORM 查询说明
	1.GORM 查询数据本质上就是提供一组函数，帮我们快速拼接 sql 语句，尽量减少编写 sql 语句的工作量
	2.GORM 查询结果我们一般是保存到结构体 struct 变量当中，所有在执行查询操作之前需要根据我们自己想要查询的数据定义结构体类型
	3.GROm 库是协程安全的，GORM 提供的函数可以并发的在多个协程中安全的执行
*/

/*	查询方法
	操作含义					函数								等价sql
	根据主键查询第一条记录		internal.DB.First(&user)		select * from user order by id limit 1;
	随机获取一条记录			internal.DB.Take(&user)			select * from user limit 1;
	根据主键查询最后一条记录	internal.DB.Last(&user)			select * from user order by id desc limit 1;
	查询所有记录				internal.DB.Find(&user)			select * from user
	查询指定的某条记录，仅当	internal.DB.First(&user, 10)	select * from user where id = 10;
	主键为整型时可用
*/

// GormQuery 函数用于 GORM 查询数据
func GormQuery() {
	// 定义接受查询结果的结构体变量
	user := User{}
	// 定义接受查询所有表结果的结构体切片
	var users []User
	// 根据主键查询第一条记录
	// SELECT * FROM users ORDER BY id LIMIT 1;
	if err := internal.DB.First(&user).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	// 随机获取一条记录
	// SELECT * FROM users LIMIT 1;
	if err := internal.DB.Take(&user).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	// 根据主键查询最后一条记录
	// SELECT * FROM users ORDER BY id DESC LIMIT 1;
	if err := internal.DB.Last(&user).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	// 查询所有的记录
	// SELECT * FROM user;
	if err := internal.DB.Find(&users).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	fmt.Printf("%v\n", user)
	// 查询指定的某条记录(仅当主键为整型时可用)
	// SELECT * FROM user WHERE id = 3;
	if err := internal.DB.First(&user, 3).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}

	// 输出结果
	fmt.Printf("查询的单条数据为 %v", user)
	if err := internal.DB.Find(&users).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	for _, v := range users {
		fmt.Printf("%v\n", v)
	}
	/* 错误特例
	   查询不到数据，GORM 也会当成错误处理，如果查询不到数据，上面的例子会打印查询失败 record not found
	   在实际开发中查询不到数据，我们不一定会当成错误处理，GORM 库提供了下面两种办法检测 Error 是不是查询不到数据
	*/
	err := internal.DB.Take(&user).Error
	if gorm.IsRecordNotFoundError(err) {
		fmt.Printf("查询不到数据\n")
	} else if err != nil {
		// 如果 err 非 record not found，又非 nil，那说明 sql 执行失败了
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	// 链式操作，先查询，然后检测查询结果
	if internal.DB.Take(&user).RecordNotFound() {
		fmt.Printf("查询不到数据\n")
	} else if err != nil {
		// 如果 err 非 record not found，又非 nil，那说明 sql 执行失败了
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
}

// WhereQuery 函数用于 where 条件查询
// internal.DB.Where(query interface{}, args ..interface{})
// query：sql 语句的 where 子句，where 子句中使用问号代替参数值，则表示通过 args 参数绑定参数
// args：where 子句绑定的参数，可以绑定多个参数
func WhereQuery() {
	// 定义接收查询结果的结构体变量
	user := User{}
	// 定义接收查询所有结果的结构体切片
	var users []User

	// 获取第一条匹配的数据
	// SELECT * FROM user WHERE name = '卢友明' LIMIT 1;
	internal.DB.Where("name = ?", "卢友明")

	// 获取所有匹配的数据，接受者是一个对应类型的的切片 []User
	// SELECT * FROM user WHERE name = '卢友明';
	internal.DB.Where("name = ?", "卢友明").First(&users)

	// 拼接 <> 排除不想要的数据，<>：不等于
	// SELECT * FROM user WHERE name <> '卢友明'；
	internal.DB.Where("name <> ?", "卢友明").Find(&users)

	// 拼接 IN 条件 sql 语句
	// SELECT * FROM user WHERE name in ('卢友明', '闫宝金');
	internal.DB.Where("name in (?)", []string{"卢友明", "闫宝金"}).Find(&users)

	// 拼接 LIKE 模糊查询
	// SELECT * FROM user WHERE name LIKE '%卢%';
	internal.DB.Where("name like ?", "%卢%").Find(&users)

	// 拼接 AND 条件查询
	// SELECT * FROM user WHERE name = '卢友明' AND age >= 23;
	internal.DB.Where("name = ? and age >= ?", "卢友明", 23).Find(&users)

	// 拼接 BETWEEN 范围查询
	// SELECT * FROM user WHERE name BETWEEN 10 AND 23;
	internal.DB.Where("age between ? and ?", 10, 23).Find(&users)

	// 拼接 TIME 条件查询
	// SELECT * FROM user FROM WHERE create_time > '2023-07-21 00:00:00';
	internal.DB.Where("create_time > ?", "2023-07-21 00:00:00").Find(&users)

	// Struct 结构体
	// SELECT * FROM user WHERE name = '卢友明' AND age = 23 LIMIT = 1;
	internal.DB.Where(&User{Name: "卢友明", Age: 23}).First(&user)

	// Map 类型
	// SELECT * FROM user WHERE name = "李欣伟" AND age = 22;
	internal.DB.Where(map[string]interface{}{"name": "李欣伟", "age": 22}).Find(&users)

	// 主键的切片
	// SELECT * FROM user WHERE id IN (2, 3, 5);
	internal.DB.Where([]int64{2, 3, 5}).Find(&users)

	// 注意：当通过结构体进行查询时，GORM 将会只通过非零值字段查询
	// 这意味着如果你的字段值为 0，''，false 或其他零值时，将不会被用于构建查询条件
	// SELECT * FROM user WHERE name = '卢友明';
	internal.DB.Where(&User{Name: "卢友明", Age: 0}).Find(&users)

	// 输出结果
	fmt.Printf("查询的单条数据为 %v", user)
	if err := internal.DB.Find(&users).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	for _, v := range users {
		fmt.Printf("%v\n", v)
	}
}

// NotQuery 函数用于 NOT 条件查询
// internal.DB.NOT(query interface{}, args ..interface{})
func NotQuery() {
	// 定义接受查询结果的结构体变量
	user := User{}
	// 定义接受查询所有表结果的结构体切片
	var users []User

	// SELECT * FROM user WHERE name <> "卢友明" LIMIT 1;
	internal.DB.Not("name", "卢友明").First(&user)

	// SELECT * FROM user WHERE name NOT IN ("卢友明", "闫宝金");
	internal.DB.Not("name", []string{"卢友明", "闫宝金"}).Find(&users)

	// NOT IN 在切片中的应用。主键的切片
	// SELECT * FROM user WHERE id NOT IN (2, 3, 5);
	internal.DB.Not([]int64{2, 3, 5}).Find(&users)

	// SELECT * FROM user LIMIT 1;
	internal.DB.Not([]int64{}).First(&user)

	// SELECT * FROM user WHERE NOT(name = "卢友明") LIMIT 1;
	internal.DB.Not("name = ?", "卢友明").First(&user)

	// Struct 结构体
	// SELECT * FROM user WHERE name <> "卢友明" LIMIT 1;
	internal.DB.Not(&User{Name: "卢友明"}).First(&user)

	// 输出结果
	fmt.Printf("查询的单条数据为 %v", user)
	if err := internal.DB.Find(&users).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	for _, v := range users {
		fmt.Printf("%v\n", v)
	}
}

// OrQuery 函数用于 Or 条件查询
// internal.DB.Or(query interface{}, args ..interface{})
func OrQuery() {
	// 定义接受查询所有表结果的结构体切片
	var users []User

	// SELECT * FROM user WHERE name = '卢友明' OR name = '闫宝金';
	internal.DB.Or("name = ?", "卢友明").Or("name = ?", "闫宝金").Find(&users)
	// Struct 结构体
	internal.DB.Or("name = ?", "卢友明").Or(&User{Name: "闫宝金"}).Find(&users)
	// map 类型
	internal.DB.Or("name = ?", "卢友明").Or(map[string]interface{}{"name": "闫宝金"}).Find(&users)

	// 输出结果
	if err := internal.DB.Find(&users).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	for _, v := range users {
		fmt.Printf("%v\n", v)
	}
}

// SelectQuery 函数用于指定你想从数据库中检索出的字段，默认会选择全部字段
func SelectQuery() {
	// 定义接受查询所有表结果的结构体切片
	var users []User

	// 查询指定字段，其他字段为每个类型的零值
	// SELECT name, age FROM user;
	internal.DB.Select("name, age").Find(&users)
	// 拼接 WHERE 条件查询指定字段
	// SELECT name, age FROM user WHERE age = 23;
	internal.DB.Select("name, age").Find(&users, "age= ?", 23)

	// SELECT name, create_time FROM user;
	internal.DB.Select([]string{"name", "create_time"}).Find(&users)

	// 指定表名和指定表字段查询
	// SELECT COALESCE(age, '22') FROM user;
	if _, err := internal.DB.Table("user").Select("COALESCE(age, ?)", 22).Rows(); err != nil {
		return
	}

	// 输出结果
	if err := internal.DB.Find(&users).Error; err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
	}
	for _, v := range users {
		fmt.Printf("%v\n", v)
	}
}

// Order 函数用于排序
func Order() {
	// 定义接收查询结果的结构体变量
	// 定义接收查询所有表结果的结构体切片
	var users []User
	var user1 []User
	var user2 []User

	// 年龄倒序排
	// SELECT * FROM user ORDER BY age desc;
	internal.DB.Order("age desc").Find(&users)

	// 多字段排序，先倒序排年龄，再倒序排id
	// SELECT * FROM user ORDER BY age desc, name;
	internal.DB.Order("age desc").Order("id desc").Find(&users)
	/* [{5 仝明昊 25 2023-07-24 10:34:26 +0800 CST}
	    {2 闫宝金 24 2023-07-24 10:25:21 +0800 CST}
		{6 陈建辉 23 2023-07-24 10:34:27 +0800 CST}
		{4 张德兼 23 2023-07-24 10:34:24 +0800 CST}
		{1 卢友明 23 2023-07-24 10:31:25 +0800 CST}
		{3 李欣伟 22 2023-07-24 10:33:03 +0800 CST}]	*/

	// 覆盖排序
	// 设置第二个参数 reorder 为 true，可以覆盖前面定义的排序条件
	// SELECT * FROM user ORDER BY age desc; (user1)
	// SELECT * FROM user ORDER BY age; (user2)
	internal.DB.Order("age desc").Find(&user1).Order("age", true).Find(&user2)
	fmt.Printf("%v\n", users)
	fmt.Printf("%v\n", user1)
	/* [{5 仝明昊 25 2023-07-24 10:34:26 +0800 CST}
	    {2 闫宝金 24 2023-07-24 10:25:21 +0800 CST}
		{1 卢友明 23 2023-07-24 10:31:25 +0800 CST}
	 	{4 张德兼 23 2023-07-24 10:34:24 +0800 CST}
	 	{6 陈建辉 23 2023-07-24 10:34:27 +0800 CST}
	 	{3 李欣伟 22 2023-07-24 10:33:03 +0800 CST}]	*/
	fmt.Printf("%v\n", user2)
	/* [{3 李欣伟 22 2023-07-24 10:33:03 +0800 CST}
	    {1 卢友明 23 2023-07-24 10:31:25 +0800 CST}
		{4 张德兼 23 2023-07-24 10:34:24 +0800 CST}
		{6 陈建辉 23 2023-07-24 10:34:27 +0800 CST}
		{2 闫宝金 24 2023-07-24 10:25:21 +0800 CST}
		{5 仝明昊 25 2023-07-24 10:34:26 +0800 CST}]	*/
}

// Limit 函数用于指定从数据库检索出的最大记录数，Count 加 model 能获取的记录总数
func Limit() {
	// 定义接收查询所有表结果的结构体切片
	var users []User
	var user1 []User
	var user2 []User

	// SELECT * FROM user LIMIT 3;
	internal.DB.Limit(3).Find(&users)
	/* [{1 卢友明 23 2023-07-24 10:31:25 +0800 CST}
	    {2 闫宝金 24 2023-07-24 10:25:21 +0800 CST}
		{3 李欣伟 22 2023-07-24 10:33:03 +0800 CST}]	*/

	// -1 取消 Limit 条件
	// SELECT * FROM user LIMIT 10; (user1)
	// [{1 卢友明 23 2023-07-24 10:31:25 +0800 CST} {2 闫宝金 24 2023-07-24 10:25:21 +0800 CST}]
	// SELECT * FROM user; (user2)
	/* [{1 卢友明 23 2023-07-24 10:31:25 +0800 CST}
	    {2 闫宝金 24 2023-07-24 10:25:21 +0800 CST}
		{3 李欣伟 22 2023-07-24 10:33:03 +0800 CST}
		{4 张德兼 23 2023-07-24 10:34:24 +0800 CST}
		{5 仝明昊 25 2023-07-24 10:34:26 +0800 CST}
		{6 陈建辉 23 2023-07-24 10:34:27 +0800 CST}]	*/
	internal.DB.Limit(2).Find(&user1).Limit(-1).Find(&user2)

}

// Count 函数用于获取能获取的记录总数
func Count() {
	// 定义接收查询所有表结果的结构体切片
	var users []User
	var count int

	// SELECT count(*) FROM user WHERE name = '卢友明' OR name = '闫宝金'; (users)(count)
	// users = [{1 卢友明 23 2023-07-24 10:31:25 +0800 CST} {2 闫宝金 24 2023-07-24 10:25:21 +0800 CST}]
	// count = 2
	internal.DB.Or("name = ?", "卢友明").Or("name = ?", "闫宝金").Find(&users).Count(&count)

	// SELECT count(*) FROM user WHERE name = '卢友明'; (count)
	// count = 1
	// 若没有 Model 方法，count 将不会赋值，count 的值仍然为上面的 2
	internal.DB.Model(&User{}).Where("name = ?", "卢友明").Count(&count)

	// 指定表名
	// SELECT count(*) FROM user; (count)
	// count = 6
	internal.DB.Table("user").Count(&count)

	// 统计有条件
	// SELECT count( distinct(name) ) FROM user; (count)
	// distinct 关键字是用于去除重复的数据记录
	// 因为没有重复的，所以 count = 6
	internal.DB.Select("count(distinct(name))").Count(&count)
}

// GroupAndHaving 函数
// 注意点
// Group 函数必须搭配 Select 函数一起使用
// 下面例子 scan 类似 Find 都是用于执行查询语句，然后把查询结果赋值给结构体变量，区别在于 scan 不会从传递进来的结构体变量提取表名
// 下面例子重新定义了一个结构体用于保存结果，但是这个结构体并没有绑定 user 表，所以这里只能使用 scan 查询函数
func GroupAndHaving() {
	// 定义一个 Result 结构体类型，用来保存查询结果
	type Result struct {
		Date  time.Time
		Total int
	}

	// 查询表中的创建时间（以天为单位），以及在当天创建的用户的所有年龄总和
	// SELECT date(create_time) AS date, sum(age) AS total FROM user GROUP BY date(create_time);
	// result = {2023-07-24 00:00:00 +0800 CST 140}		24 号创建的用户所有年龄总和为 140
	// result = {2023-07-22 00:00:00 +0800 CST 23}		22 号创建的用户所有年龄总和为 23
	rows, err := internal.DB.Table("user").Select("date(create_time) as date, sum(age) as total").
		Group("date(create_time)").Rows()
	if err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
		return
	}
	for rows.Next() {
		var result Result
		if err := rows.Scan(&result.Date, &result.Total); err != nil {
			fmt.Printf("scan失败！\nError: %v\n", err)
		}
		fmt.Printf("result = %v\n", result)
	}

	// 查询表中的创建时间（以天为单位），以及在当天创建的用户的所有年龄总和，且只查询年龄总和大于 100 的日期
	// SELECT date(create_time) AS date, sum(age) AS total FROM user GROUP BY date(create_time) HAVING (sum(age) > 100);
	// result = {2023-07-24 00:00:00 +0800 CST 140}
	// 24 号创建的用户所有年龄总和为 140，22 号年龄总和不超过 100，所以不传递给 result
	rows, err = internal.DB.Table("user").Select("date(create_time) as date, sum(age) as total").
		Group("date(create_time)").Having("sum(age) > ?", 100).Rows()
	if err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
		return
	}
	for rows.Next() {
		var result Result
		if err := rows.Scan(&result.Date, &result.Total); err != nil {
			fmt.Printf("scan失败！\nError: %v\n", err)
		}
		fmt.Printf("result = %v\n", result)
	}

	// 下面两种方式类似
	// scan 类似 Find 都是用于执行查询语句，然后把查询的结果赋值给结构体变量，区别在于 scan 不会从传递进来的结构体变量提取表名
	// 这里因为重新定义了一个结构体用于保存结果，但是这个结构体并没有绑定 user 表，所有这里只能使用 scan 查询函数
	var results []Result

	// result = {2023-07-24 00:00:00 +0800 CST 140}
	// 24 号创建的用户所有年龄总和为 140，22 号年龄总和不超过 100，所以不传递给 result
	// SELECT date(create_time) AS date, sum(age) AS total FROM user GROUP BY date(create_time) HAVING (sum(age) > 100);
	internal.DB.Model(User{}).Select("date(create_time) as date, sum(age) As total").
		Group("date(create_time)").Having("sum(age) > ?", 100).Scan(&results)
	internal.DB.Table("user").Select("date(create_time) as date, sum(age) As total").
		Group("date(create_time)").Having("sum(age) > ?", 100).Scan(&results)
}

// Join 函数用于指定条件
func Join() {
	type Result struct {
		Name  string
		Email string
	}

	// SELECT user.name, email.email FROM user LEFT JOIN email on email on email.user_id = user.id;
	rows, err := internal.DB.Table("user").Select("user.name, email.email").
		Joins("left join email on email.user_id = user.id").Rows()
	if err != nil {
		fmt.Printf("查询失败！\nError: %v\n", err)
		return
	}
	for rows.Next() {
		var result Result
		if err := rows.Scan(&result.Name, &result.Email); err != nil {
			fmt.Printf("scan失败！\nError: %v\n", err)
		}
		fmt.Printf("result = %v\n", result)
	}

	var results []Result
	// SELECT user.name, email.email FROM users LEFT JOIN email on email.user_id = user.id;
	internal.DB.Table("user").Select("user.name, email.email").
		Joins("left join email on email.user_id = user.id").Scan(&results)

	var user User
	// 多连接及参数
	internal.DB.Joins("JOIN email ON email.user_id = user.id AND email.email = ?", "wy175621@163.com").
		Joins("JOIN credit_card ON credit_card.user_id = user.id").
		Where("credit_card.number = ?", 13611110360).Find(&user)
}
